#include <buola/gui.h>

#include <buola/widgets/cscrollbar.h>
#include <buola/widgets/ctheme.h>

namespace buola { namespace gui {

const double MIN_CURSOR_SIZE=30.0;

CScrollBar::CScrollBar()
    :   mDragging(false),
        mInside(false),
        mDontUpdateBar(false)
{
    CSize_d lButtonSize=get_default_theme()->GetScrollBarButtonSize();
    Resize(CSize_d(lButtonSize.x,lButtonSize.x));
    get_default_theme()->PrepareScrollBar(this);

    CalcBar();

    SetPointer(CPointer::STOCK_DEFAULT);
}

CScrollBar::~CScrollBar()
{}

void CScrollBar::CalcBar()
{
    CSize_d lButtonSize=get_default_theme()->GetScrollBarButtonSize();
    if(GetStyle(STYLE_HORIZONTAL))
    {
        mBarTop=lButtonSize.y;
        mBarBottom=Size().x-lButtonSize.y;
    }
    else
    {
        mBarTop=lButtonSize.y;
        mBarBottom=Size().y-lButtonSize.y;
    }

    double lHeight=mBarBottom-mBarTop;
    int lTotal=GetMax()-GetMin()+GetBigStep();

    if(lTotal>0&&GetMax()>GetMin())
    {
        //make sure cursor is at least MIN_CURSOR_SIZE, but never force it
        //this way to be more than half the height
        double lCursorSize=max(GetBigStep()*lHeight/lTotal,
                        min(MIN_CURSOR_SIZE,lHeight/2));

        mCursorTop=mBarTop+(GetValue()-GetMin())*(lHeight-lCursorSize)/(GetMax()-GetMin());
        mCursorBottom=mCursorTop+lCursorSize;
    }
    else
    {
        mCursorTop=mBarTop;
        mCursorBottom=mBarBottom;
    }
}

void CScrollBar::CalcPos()
{
    //CSize_d lButtonSize=get_default_theme()->GetScrollBarButtonSize();
    double lHeight=mBarBottom-mBarTop-(mCursorBottom-mCursorTop);
    int lTotal=GetMax()-GetMin();
    if(lTotal<0) return;
    double lBefore=mCursorTop-mBarTop;

    mDontUpdateBar=true;

    if(lBefore>0&&mCursorBottom==mBarBottom)
        SetValue(GetMax());
    else
        SetValue(GetMin()+lBefore*lTotal/lHeight);

    mDontUpdateBar=false;
}

void CScrollBar::Scroll(int pAmount)
{
    IncValue(pAmount);
}

void CScrollBar::Shape()
{
    CalcBar();
}

///////////////////// Event handling //////////////////////

void CScrollBar::OnExpose(const CExposeEvent &pE)
{
    CTheme::EDrawStyle lFlags=CTheme::DRAW_NONE;
    if(mInside)
        lFlags|=CTheme::DRAW_HOVER;
    if(mDragging)
        lFlags|=CTheme::DRAW_HOVER|CTheme::DRAW_PRESSED;
    get_default_theme()->DrawScrollBar(pE.mGC,this,lFlags);
}

void CScrollBar::OnButtonDown(const CMouseEvent &pE)
{
    if(pE.mButton==EMouseButton::LEFT)
    {
        if(mDragging) return;
        double lPos;

        if(GetStyle(STYLE_HORIZONTAL))
            lPos=pE.mPoint.x;
        else
            lPos=pE.mPoint.y;

        if(lPos<mBarTop)
        {
            IncValue(-GetSmallStep());
        }
        else if(lPos<mCursorTop)
        {
            IncValue(-GetBigStep());
        }
        else if(lPos<=mCursorBottom)
        {
            mDragStart=lPos;
            mDragPos=mDragStart;
            mDragging=true;
        }
        else if(lPos<=mBarBottom)
        {
            IncValue(GetBigStep());
        }
        else
        {
            IncValue(GetSmallStep());
        }

        ///\todo
        //if(!mDragging)
        //    RepeatButtonEvent(pE);
    }
}

void CScrollBar::OnButtonUp(const CMouseEvent &pE)
{
    if(!mDragging) return;
    mDragging=false;

    CalcPos();
    Refresh();
}

void CScrollBar::OnMouseIn(const CMouseEvent &pE)
{
    mInside=true;
    Refresh();
}

void CScrollBar::OnMouseOut(const CMouseEvent &pE)
{
    mInside=false;
    Refresh();
}

void CScrollBar::OnMouseMove(const CMouseEvent &pE)
{
    if(!mDragging) return;

    double lPos;

    if(GetStyle(STYLE_HORIZONTAL))
        lPos=pE.mPoint.x;
    else
        lPos=pE.mPoint.y;

    if(lPos==mDragPos) return;
    mCursorTop+=lPos-mDragPos;
    mCursorBottom+=lPos-mDragPos;
    mDragPos=lPos;

    if(mCursorTop<mBarTop)
    {
        mDragPos+=mBarTop-mCursorTop;
        mCursorBottom+=mBarTop-mCursorTop;
        mCursorTop=mBarTop;
    }
    else if(mCursorBottom>mBarBottom)
    {
        mDragPos-=mCursorBottom-mBarBottom;
        mCursorTop-=mCursorBottom-mBarBottom;
        mCursorBottom=mBarBottom;
    }

    CalcPos();
    Refresh();
}

void CScrollBar::OnMouseWheel(const CMouseEvent &pE)
{
    if(pE.mScrollY!=0)
        IncValue(pE.mScrollY*GetSmallStep());
}

void CScrollBar::OnFocusIn(const CFocusEvent &pE)
{
}

void CScrollBar::OnFocusOut(const CFocusEvent &pE)
{
}

void CScrollBar::OnKeyDown(const CKeyEvent &pE)
{
    if(pE.mKey==KEY_PGUP)
    {
        IncValue(-GetBigStep());
    }
    if(pE.mKey==KEY_PGDN)
    {
        IncValue(GetBigStep());
    }

    if(GetStyle(STYLE_HORIZONTAL))
    {
        if(pE.mKey==KEY_LEFT)
        {
            IncValue(-GetSmallStep());
        }
        if(pE.mKey==KEY_RIGHT)
        {
            IncValue(GetSmallStep());
        }
    }
    else
    {
        if(pE.mKey==KEY_UP)
        {
            IncValue(-GetSmallStep());
        }
        if(pE.mKey==KEY_DOWN)
        {
            IncValue(GetSmallStep());
        }
    }
}

void CScrollBar::OnSize(const CGeometryEvent &pE)
{
    Shape();
}

void CScrollBar::OnStyleChanged(TStyle pOld)
{
    int lChange=pOld^GetStyle();

    if(lChange&STYLE_FAKE)
    {
        if(GetStyle(STYLE_FAKE))
        {
            SetInputMode(EInputMode::MOUSETRANSPARENT);
        }
        else
        {
            SetInputMode(EInputMode::NORMAL);
        }
    }
    if(lChange&STYLE_HORIZONTAL)
    {
        Shape();
        Refresh();
    }
}

void CScrollBar::OnSWChange()
{
    if(mDontUpdateBar) return;

    CalcBar();
    Refresh();
}

/*namespace gui*/ } /*namespace buola*/ }
